/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.liveSense.server.i18n.loader;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import java.util.StringTokenizer;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.nodetype.NodeType;
import org.apache.commons.lang.LocaleUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.jcr.api.SlingRepository;
import org.liveSense.server.i18n.I18N;
import org.liveSense.server.i18n.service.I18nService.I18nService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>I18nLoader</code> is the service
* providing the following functionality:
* <ul>
* <li>Bundle listener to load initial I18n resource bundles for bundles.
* <li>Fires OSGi EventAdmin events on behalf of internal helper objects
* </ul>
**/
@Component(label="%i18nLoader.name", description="%i18nLoader.description",
immediate=true, metatype=true, configurationFactory=true, policy=ConfigurationPolicy.OPTIONAL, createPid=false)
@Properties(value={
@Property(name=I18nLoader.PROP_CONFIGURATION_I18N_PATH, value=I18nLoader.DEFAULT_CONFIGURATION_I18N_PATH)
})
public class I18nLoader implements SynchronousBundleListener {
Logger log = LoggerFactory.getLogger(I18nLoader.class);
private final static String CONFIGURATION_PROPERTY_NAME = "i18nConfigLoaderName";
public final static String PROP_CONFIGURATION_I18N_PATH = "resourcePath";
public final static String DEFAULT_CONFIGURATION_I18N_PATH = "i18n";
@Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC)
ConfigurationAdmin configurationAdmin;
@Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC)
I18nService i18nService;
@Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC)
SlingRepository repository;
private String path = DEFAULT_CONFIGURATION_I18N_PATH;
// ---------- BundleListener -----------------------------------------------
/**
* Loads and unloads any configuration provided by the bundle whose state
* changed. If the bundle has been started, the configuration is loaded. If
* the bundle is about to stop, the configurations are unloaded.
*
* @param event The <code>BundleEvent</code> representing the bundle state
* change.
*/
@Override
public void bundleChanged(BundleEvent event) {
//
// NOTE:
// This is synchronous - take care to not block the system !!
//
switch (event.getType()) {
case BundleEvent.STARTING:
try {
registerBundle(event.getBundle());
} catch (Throwable t) {
log.error(
"bundleChanged: Problem loading I18n configuration of bundle "
+ event.getBundle().getSymbolicName() + " ("
+ event.getBundle().getBundleId() + ")", t);
} finally {
}
break;
case BundleEvent.STOPPED:
try {
unregisterBundle(event.getBundle());
} catch (Throwable t) {
log.error(
"bundleChanged: Problem unloading I18n configuration of bundle "
+ event.getBundle().getSymbolicName() + " ("
+ event.getBundle().getBundleId() + ")", t);
} finally {
}
break;
}
}
@Activate
public void activate(ComponentContext context) throws Exception {
context.getBundleContext().addBundleListener(this);
path = PropertiesUtil.toString(context.getProperties().get(PROP_CONFIGURATION_I18N_PATH), DEFAULT_CONFIGURATION_I18N_PATH);
int ignored = 0;
try {
Bundle[] bundles = context.getBundleContext().getBundles();
for (int i=0;i<bundles.length; i++) {
Bundle bundle = bundles[i];
if ((bundle.getState() & (Bundle.ACTIVE)) != 0) {
// load configurations from bundles which are ACTIVE
try {
registerBundle(bundle);
} catch (Throwable t) {
log.error(
"Problem loading I18n configuration of bundle "
+ bundle.getSymbolicName() + " ("
+ bundle.getBundleId() + ")", t);
} finally {
}
} else {
ignored++;
}
if ((bundle.getState() & (Bundle.ACTIVE)) == 0) {
// remove configurations from bundles which are not ACTIVE
try {
unregisterBundle(bundle);
} catch (Throwable t) {
log.error(
"Problem loading I18n configuration of bundle "
+ bundle.getSymbolicName() + " ("
+ bundle.getBundleId() + ")", t);
} finally {
}
} else {
ignored++;
}
}
log.info(
"Out of "+bundles.length+" bundles, "+ignored+" were not in a suitable state for I18n configuration loading");
} catch (Throwable t) {
log.error("activate: Problem while loading I18n configuration", t);
} finally {
}
}
@Deactivate
public void deactivate(BundleContext context) throws Exception {
context.removeBundleListener(this);
int ignored = 0;
try {
Bundle[] bundles = context.getBundles();
for (int i=0;i<bundles.length; i++) {
Bundle bundle = bundles[i];
if ((bundle.getState()) == 0) {
// remove configurations from bundles which are not ACTIVE
try {
unregisterBundle(bundle);
} catch (Throwable t) {
log.error(
"Problem loading I18n configuration of bundle "
+ bundle.getSymbolicName() + " ("
+ bundle.getBundleId() + ")", t);
} finally {
}
} else {
ignored++;
}
}
log.info(
"Out of "+bundles.length+" bundles, "+ignored+" were not in a suitable state for I18n configuration loading");
} catch (Throwable t) {
log.error("activate: Problem while loading I18n configuration", t);
} finally {
}
}
// ---------- Implementation helpers --------------------------------------
/**
* Register a bundle and install the configurations included them.
*
* @param bundle
*/
public void registerBundle(final Bundle bundle) throws Exception {
// if this is an update, we have to uninstall the old content first
log.debug("Registering bundle "+bundle.getSymbolicName()+" for I18n configuration loading.");
registerBundleInternal(bundle);
/*
if (registerBundleInternal(bundle)) {
// handle delayed bundles, might help now
int currentSize = -1;
for (int i = delayedBundles.size(); i > 0
&& currentSize != delayedBundles.size()
&& !delayedBundles.isEmpty(); i--) {
Iterator di = delayedBundles.iterator();
while (di.hasNext()) {
Bundle delayed = (Bundle)di.next();
if (registerBundleInternal(delayed)) {
di.remove();
}
}
currentSize = delayedBundles.size();
}
} else {
// add to delayed bundles - if this is not an update!
delayedBundles.add(bundle);
}*/
}
private boolean registerBundleInternal(
final Bundle bundle) throws Exception {
// check if bundle has initial configuration
final Iterator<?> pathIter = PathEntry.getEntries(bundle);
if (pathIter == null) {
log.debug("Bundle "+bundle.getSymbolicName()+" has no I18n configuration(s)");
return true;
}
log.info("Bundle "+bundle.getSymbolicName()+" has I18n configuration(s)");
while (pathIter.hasNext()) {
PathEntry path = (PathEntry)pathIter.next();
String i18nBundleName = path.getPath();
install(bundle, i18nBundleName);
}
return false;
}
/**
* Unregister a bundle. Remove installed content.
*
* @param bundle The bundle.
*/
public void unregisterBundle(final Bundle bundle) throws Exception {
final Iterator<?> pathIter = PathEntry.getEntries(bundle);
if (pathIter == null) {
log.debug("Bundle "+bundle.getSymbolicName()+" has no I18n configuration(s)");
return;
}
log.info("Bundle "+bundle.getSymbolicName()+" has I18n configuration(s)");
while (pathIter.hasNext()) {
PathEntry path = (PathEntry)pathIter.next();
String i18nBundleName = path.getPath();
uninstall(bundle, i18nBundleName);
}
}
private static final String FOLDER_NODE_TYPE = "nt:unstructured";
/**
* Creates or gets the {@link javax.jcr.Node Node} at the given Path.
*
* @param session The session to use for node creation
* @param absolutePath absolute node path
* @param nodeType to use for creation of the final node
* @return the Node at path
* @throws RepositoryException in case of exception accessing the Repository
*/
private Node createPath(final Session session,
final String absolutePath,
final String nodeType)
throws RepositoryException {
final Node parentNode = session.getRootNode();
String relativePath = absolutePath.substring(1);
if (!parentNode.hasNode(relativePath)) {
Node node = parentNode;
int pos = relativePath.lastIndexOf('/');
if ( pos != -1 ) {
final StringTokenizer st = new StringTokenizer(relativePath.substring(0, pos), "/");
while ( st.hasMoreTokens() ) {
final String token = st.nextToken();
if ( !node.hasNode(token) ) {
try {
node.addNode(token, FOLDER_NODE_TYPE);
} catch (RepositoryException re) {
// we ignore this as this folder might be created from a different task
node.refresh(false);
}
}
node = node.getNode(token);
}
relativePath = relativePath.substring(pos + 1);
}
if ( !node.hasNode(relativePath) ) {
node.addNode(relativePath, nodeType);
}
return node.getNode(relativePath);
}
return parentNode.getNode(relativePath);
}
public void install(Bundle bundle, String bundleName) throws Exception {
// Removes proxy classes
I18N.resetCache();
log.info("Registering I18n: "+bundleName);
i18nService.registerResourceBundle(bundle, bundleName);
Session session = null;
try {
session = repository.loginAdministrative(null);
// Writing entries to Repository
String i18nPath = new File(bundleName.replace(".", "/")).getParent();
String i18nName = new File(bundleName.replace(".", "/")).getName();
Enumeration entries = bundle.getEntryPaths(i18nPath);
if (entries != null) {
while (entries.hasMoreElements()) {
URL url = bundle.getEntry((String)entries.nextElement());
String urlFileName = new File(url.getFile()).getName();
if (urlFileName.endsWith(".properties") && (urlFileName.startsWith(i18nName))) {
log.info("Loading "+url+" into JCR repository");
String locale = urlFileName.substring(i18nName.length(), urlFileName.length()-".properties".length());
Locale loc = Locale.getDefault();
if (StringUtils.isNotEmpty(locale)) {
loc = LocaleUtils.toLocale(locale.substring(1));
Node n = createPath(session,"/"+path+"/"+bundleName+"/"+loc.toString(), FOLDER_NODE_TYPE);
boolean foundType = false;
for (NodeType t : n.getMixinNodeTypes()) {
if (t.getName().equals(NodeType.MIX_LANGUAGE)) {
foundType = true;
}
}
if (!foundType)
n.addMixin(NodeType.MIX_LANGUAGE);
n.setProperty("jcr:language", loc.toString());
n.setProperty("sling:basename", bundleName);
java.util.Properties props = new java.util.Properties();
InputStream in = url.openStream();
props.load(in);
in.close();
for (Object key : props.keySet()) {
if (!n.hasNode((String)key)) {
log.info("Creating "+(String)key);
Node msgNode = n.addNode((String)key, "sling:MessageEntry");
msgNode.setProperty("sling:key", (String)key);
msgNode.setProperty("sling:message", props.getProperty((String) key));
}
}
}
}
}
}
if (session.hasPendingChanges())
session.save();
} catch (RepositoryException e) {
log.error("Cannot get session", e);
} finally {
if (session != null && session.isLive()) {
try {
session.logout();
} catch (Exception e) {
}
}
}
}
public void uninstall(Bundle bundle, String bundleName) throws Exception {
log.info("UnRegistering I18n: "+bundleName);
i18nService.unregisterResourceBundle(bundle, bundleName);
}
}